package com.ms.service.module.broadcastlistener;

import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import redis.clients.jedis.Jedis;

import com.ms.service.db.jedis.JedisManager;
import com.ms.service.module.account.message.SCharacterLoginNotify;
import com.ms.service.module.account.message.SSessionCloseNotify;
import com.xpec.c4.game.time.TimeSys;
import com.xpec.c4.game.utility.GUID;
import com.xpec.c4.message.MessageTypes;
import com.xpec.c4.service.module.BasicServiceModule;
import com.xpec.c4.service.module.ModuleName;

/**
 * 登入登出廣播收聽管理<br>
 * 以讀取redis的方式傳送登入及登出的廣播消息
 */
public class LoginLogoutListener {
	
	private static final Log log = LogFactory.getLog(LoginLogoutListener.class);

	/**需要收聽玩家登入的modules*/
	private static final String LOGIN_PLAYER_LISTENER = "LogInListener:Player:";
	
	/**需要收聽玩家登出的modules*/
	private static final String LOGOUT_PLAYER_LISTENER = "LogOutListener:Player:";
	
	/**需要收聽連線登出的modules*/
	private static final String LOGOUT_SESSION_LISTENER = "LogOutListener:Session:";
	
	public static final String MULTI_KEY_SPLITER = ";";
	
	/**新增進redis*/
	public static final int ADD = 1;
	
	/**從redis移除*/
	public static final int REMOVE = 2;
	
	/**移除redis該key*/
	public static final int DELETE_KEY = 3;
	
	/**
	 * 新增收聽玩家登入的module，請在合理的地方加入移除的方法<br>
	 * com.ms.service.broadcastlistener.LoginLogoutListener.removeLogInPlayerListener(BasicServiceModule, GUID)<br>
	 * 或<br>
	 * com.ms.service.broadcastlistener.LoginLogoutListener.removeLogInPlayerListener(BasicServiceModule, String)
	 */
	public static void addLogInPlayerListener(BasicServiceModule listenModule, GUID playerGUID){
		addAsync(LOGIN_PLAYER_LISTENER, playerGUID, ADD, listenModule.getModuleName(), listenModule);
	}
	
	/**
	 * 移除收聽玩家登入的module(GUID)<br>
	 * 如果redis中移除的element不是1個，會寫出warn層級的log，可以藉此檢視在移除的邏輯是否正確
	 */
	public static void removeLogInPlayerListener(BasicServiceModule listenModule, GUID playerGUID){
		addAsync(LOGIN_PLAYER_LISTENER, playerGUID, REMOVE, listenModule.getModuleName(), listenModule);
	}
	
	/**
	 * 移除收聽玩家登入的module(String)<br>
	 * 如果redis中移除的element不是1個，會寫出warn層級的log，可以藉此檢視在移除的邏輯是否正確
	 */
	public static void removeLogInPlayerListener(BasicServiceModule listenModule, String playerGUID){
		addAsync(LOGIN_PLAYER_LISTENER, GUID.parseUUID(playerGUID), REMOVE, listenModule.getModuleName(), listenModule);
	}
	
//	/**
//	 * 清空收聽玩家登入的modules
//	 */
//	public static void clearLogInPlayerListener(GUID playerGUID, BasicServiceModule module){
//		addAsync(LOGIN_PLAYER_LISTENER, playerGUID, DELETE_KEY, null, module);
//	}
	
	/**
	 * 新增收聽玩家登出的module，請在合理的地方加入移除的方法<br>
	 * com.ms.service.module.broadcastlistener.LoginLogoutListener.removeLogOutPlayerListener(BasicServiceModule, GUID)
	 */
	public static void addLogOutPlayerListener(BasicServiceModule listenModule, GUID playerGUID){
		addAsync(LOGOUT_PLAYER_LISTENER, playerGUID, ADD, listenModule.getModuleName(), listenModule);
	}
	
	/**
	 * 移除收聽玩家登出的module<br>
	 * 如果redis中移除的element不是1個，會寫出warn層級的log，可以藉此檢視在移除的邏輯是否正確
	 */
	public static void removeLogOutPlayerListener(BasicServiceModule listenModule, GUID playerGUID){
		addAsync(LOGOUT_PLAYER_LISTENER, playerGUID, REMOVE, listenModule.getModuleName(), listenModule);
	}
	
//	/**
//	 * 清空收聽玩家登出的modules
//	 */
//	public static void clearLogOutPlayerListener(GUID playerGUID, BasicServiceModule module){
//		addAsync(LOGOUT_PLAYER_LISTENER, playerGUID, DELETE_KEY, null, module);
//	}
	
	/**
	 * 新增收聽連線登出的module，請在合理的地方加入移除的方法<br>
	 * com.ms.service.module.broadcastlistener.LoginLogoutListener.removeLogOutSessionListener(BasicServiceModule, GUID)
	 */
	public static void addLogOutSessionListener(BasicServiceModule listenModule, GUID sessionID){
		addAsync(LOGOUT_SESSION_LISTENER, sessionID, ADD, listenModule.getModuleName(), listenModule);
	}
	
	/**
	 * 移除收聽連線登出的module<br>
	 * 如果redis中移除的element不是1個，會寫出warn層級的log，可以藉此檢視在移除的邏輯是否正確
	 */
	public static void removeLogOutSessionListener(BasicServiceModule listenModule, GUID sessionID){
		addAsync(LOGOUT_SESSION_LISTENER, sessionID, REMOVE, listenModule.getModuleName(), listenModule);
	}
	
//	/**
//	 * 清空收聽連線登出的modules
//	 */
//	public static void clearLogOutSessionListener(GUID playerGUID, BasicServiceModule module){
//		addAsync(LOGOUT_SESSION_LISTENER, playerGUID, DELETE_KEY, null, module);
//	}
	
//	/**
//	 * 新增收聽玩家、連線登出的module
//	 */
//	public static void addLogOutPlayerAndSessionListener(BasicServiceModule listenerModule, GUID playerGUID, GUID sessionID){
//		String keyPlayer = LOGOUT_PLAYER_LISTENER + playerGUID.toString();
//		String keySession = LOGOUT_SESSION_LISTENER + sessionID.toString();
//		
//		String multiKey = keyPlayer + MULTI_KEY_SPLITER + keySession;
//		addMultiKeyAsync(playerGUID, multiKey, ADD, listenerModule.getModuleName(), listenerModule);
//	}
//	
//	/**
//	 * 刪除收聽玩家、連線登出的module
//	 */
//	public static void removeLogOutPlayerAndSessionListener(BasicServiceModule listenerModule, GUID playerGUID, GUID sessionID){
//		String keyPlayer = LOGOUT_PLAYER_LISTENER + playerGUID.toString();
//		String keySession = LOGOUT_SESSION_LISTENER + sessionID.toString();
//		
//		String multiKey = keyPlayer + MULTI_KEY_SPLITER + keySession;
//		addMultiKeyAsync(playerGUID, multiKey, REMOVE, listenerModule.getModuleName(), listenerModule);
//	}
	
	/**
	 * 以Async新增或移除Redis資訊
	 */
	private static void addAsync(String key, GUID subKey, int type, String moduleName, BasicServiceModule module){
		if(key == null || subKey == null || module == null){
			if(log.isErrorEnabled()){
				log.error("can't operate addAsync, some necessary param is null");
			}
			return;
		}
		
		String redisKey = key + subKey.toString();
		
		LoginLogoutRedisAsync async = new LoginLogoutRedisAsync();
		async.key = subKey;
		async.moduleServer = module;
		async.setRedisKeys(redisKey);
		async.setModuleName(moduleName);
		async.setType(type);
		module.addAsyncCommand(async);
	}
	
	/**
	 * 以Async新增或移除多個key的資訊
	 */
	private static void addMultiKeyAsync(GUID asyncKey, String keys, int type, String moduleName, BasicServiceModule module){
		if(asyncKey == null || keys == null || module == null){
			if(log.isErrorEnabled()){
				log.error("can't operate addAsync, some necessary param is null");
			}
			return;
		}
		
		LoginLogoutRedisAsync async = new LoginLogoutRedisAsync();
		async.key = asyncKey;
		async.moduleServer = module;
		async.setRedisKeys(keys);
		async.setModuleName(moduleName);
		async.setType(type);
		module.addAsyncCommand(async);
	}

	/**
	 * 廣播玩家登入訊息給紀錄的modules，處理完畢後將redis紀錄的資訊清除<br>
	 * (目前沒有收到某一個sessionID登入時要處理事情的需求，因此先只拿playerGUID當作參考送廣播)
	 */
	public static void broadcastPlayerLogIn(SCharacterLoginNotify notify, BasicServiceModule operateModule){
		if(notify == null || notify.getPlayerId() == null)
			return;
		
		GUID playerGUID = notify.getPlayerId();
		String redisKey = LOGIN_PLAYER_LISTENER + playerGUID.toString();
		broadcast(MessageTypes.S_ACCOUNT_CHARACTER_LOGIN_NOTIFY, notify, operateModule, playerGUID, redisKey);
	}
	
//	/**
//	 * 廣播玩家登出訊息給紀錄的modules<br>
//	 * 提示：不建議使用，收斷線消息的有些需要sessionID，有些需要playerGUID<br>
//	 * 建議使用broadcastPlayerAndSessionLogOut一起發送
//	 */
//	@Deprecated
//	public static void broadcastPlayerLogOut(SSessionCloseNotify notify, BasicServiceModule operateModule){
//		if(notify == null || notify.getPlayerId() == null)
//			return;
//		
//		GUID playerGUID = notify.getPlayerId();
//		String redisKey = LOGOUT_PLAYER_LISTENER + playerGUID.toString();
//		broadcast(MessageTypes.S_SESSION_CLOSE_NOTIFY, notify, operateModule, playerGUID, redisKey);
//	}
//	
//	/**
//	 * 廣播連線登出訊息給紀錄的modules
//	 */
//	public static void broadcastSessionLogOut(SSessionCloseNotify notify, BasicServiceModule operateModule){
//		if(notify == null || notify.getSessionId() == null)
//			return;
//		
//		GUID sessionID = notify.getSessionId();
//		String redisKey = LOGOUT_SESSION_LISTENER + sessionID.toString();
//		broadcast(MessageTypes.S_SESSION_CLOSE_NOTIFY, notify, operateModule, sessionID, redisKey);
//	}
	
	/**
	 * 廣播角色登出與連線登出給紀錄的moudule，處理完畢後將redis紀錄的資訊清除<br>
	 * 會先送給紀錄sessionID的modules才送給紀錄playerGUID的modules
	 */
	public static void broadcastPlayerAndSessionLogOut(SSessionCloseNotify notify, BasicServiceModule operateModule){
		if(notify == null)
			return;
		
		GUID playerGUID = notify.getPlayerId();
		GUID sessionID = notify.getSessionId();
		String redisKeyPlayerGUID = (playerGUID == null) ? "" : LOGOUT_PLAYER_LISTENER + playerGUID.toString();
		String redisKeySessionID = (sessionID == null) ? "" : LOGOUT_SESSION_LISTENER + sessionID.toString();
		String redisKeys = redisKeySessionID + MULTI_KEY_SPLITER + redisKeyPlayerGUID;
		broadcast(MessageTypes.S_SESSION_CLOSE_NOTIFY, notify, operateModule, playerGUID, redisKeys);
	}
	
	/**
	 * 送廣播
	 */
	private static void broadcast(int messageType, Object content, BasicServiceModule operateModule, GUID asyncKey, String redisKeys){
		BroadcastLoginLogoutAsync async = new BroadcastLoginLogoutAsync();
		async.key = asyncKey;
		async.moduleServer = operateModule;
		async.setRedisKeys(redisKeys);
		async.setMessageType(messageType);
		async.setBroadcast(content);
		
		//執行完之後把redis記錄刪掉(如果這裡改成false，要檢查所有收聽廣播的地方來移除)
		async.setDeleteAfterFinish(true);
		
		if(operateModule != null){
			operateModule.addAsyncCommand(async);
		}
		//如果operateModule 是 null時，直接在主執行緒執行
		else{
			if(log.isWarnEnabled()){
				log.warn("operateModule is null, try to execute BroadcastLoginLogoutAsync in main thread");
			}
			
			try {
				async.asyncExecute();
				async.execute();
			} catch (Exception e) {
				if(log.isErrorEnabled()){
					log.error("asyncExecute fail!", e);
				}
			}
		}
	}
	
	/**
	 * 這邊使用模糊搜尋，刪除所有在redis儲存的KEY
	 */
	public static void clearAllNotifyModulesInRedis(){
		Jedis jedis = JedisManager.getResource(ModuleName.LOGIN);
		
		//需要搜尋的keys
		String[] searchKeys = 
		{	
			LOGIN_PLAYER_LISTENER + "*",
			LOGOUT_PLAYER_LISTENER + "*",
			LOGOUT_SESSION_LISTENER + "*"
		};
		
		int count = 0;
		long beginTime = TimeSys.currentTimeMills();
		
		for(String searchKey : searchKeys){
			//取得全部的KEY
			Set<String> rediskeys = jedis.keys(searchKey);
			
			if(rediskeys == null || rediskeys.size() == 0)
				continue;
			
			//刪除每一個KEY
			for(String key : rediskeys){
				if(jedis.exists(key)){
					jedis.del(key);
					count ++;
				}
			}
		}
		JedisManager.returnResource(ModuleName.LOGIN, jedis);
		
		//紀錄一下處理結果
		if(log.isInfoEnabled()){
			log.info("deleted redis keys count: "+count+", cost time: "+(TimeSys.currentTimeMills() - beginTime)+" ms");
		}
	}
	
}
